"ui nodejs";
require('rhino').install();

const Snackbar = com.google.android.material.snackbar.Snackbar;

const ui = require('ui');
const app = require('app');

const {
    toColorInt
} = require('color');
const {
    showToast
} = require('toast');



class MainActivity extends ui.Activity {

    get initialStatusBar() {
        return {
            color: '#a0c2ed',
            light: true
        };
    }

    get layoutXml() {
        return `
<column>
    <appbar w="*" h="auto">
        <toolbar id="toolbar" title="3D引擎Demo  by 楚轩(恒道无惑)"/>
    </appbar>
    <scroll>
        <column bg="#ffa0a0a0">
            <text text="画布" textStyle="bold" textSize="16sp"/>
            <canvas id="canvas" w="1080px" h="1080px"/>
            <horizontal>
                <text text="fov" w="50"/>
                <text id="fov_value" w="120"/>
                <seekbar id="setCamera_fov" w="*" h="30" max="180" progress="18"/>
            </horizontal>
            <horizontal>
                <text text="aspect" w="50"/>
                <text id="aspect_value" w="120"/>
                <seekbar id="setCamera_aspect" w="*" h="30" max="200" progress="100"/>
            </horizontal>
            <horizontal>
                <text text="near" w="50"/>
                <text id="near_value" w="120"/>
                <seekbar id="setCamera_n" w="*" h="30" max="500" progress="100"/>
            </horizontal>
            <horizontal>
                <text text="far" w="50"/>
                <text id="far_value" w="120"/>
                <seekbar id="setCamera_f" w="*" h="30" max="500" progress="100"/>
            </horizontal>
            <horizontal bg="#ffffffff">
                <canvas id="rocker" w="300px" h="300px"/>
                <View bg="#ff000000" w="1" h="*"/>
                <text id="up" text="up" w="200px" h="200px" gravity="center" />
                <text id="down" text="down" w="200px" h="200px" gravity="center"/>
            </horizontal>
            
        </column>
        
    </scroll>
</column>
        `
    }

    onContentViewSet(view) {
        this.setSupportActionBar(view.findView("toolbar"));
        this._setupCanvas(view.findView("canvas"));
        this._setUpCamere(view.findView("setCamera_fov"), view.findView("setCamera_aspect"), view.findView("setCamera_n"), view.findView("setCamera_f"),view.findView("fov_value"),view.findView("aspect_value"),view.findView("near_value"),view.findView("far_value"))
        this._setUpRocker(view.findView("rocker"), view.findView("up"), view.findView("down"))

    }


    _setupCanvas(canvasView) {
        const Paint = android.graphics.Paint;
        const paint = new Paint();
        paint.setTextSize(100);
        paint.setColor(toColorInt(0xff00ff00));
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(2)

        var center = {
            x: 500,
            y: 500
        }
        var scale = 300
        canvasView.on("draw", (canvas) => {
            objs.forEach((obj, index) => {
                drawObj(canvas, paint, obj, Mortho, center, scale);
            })
        })
        let x0, y0;
        canvasView.setOnTouchListener((view, event) => {
            switch (event.getAction()) {
                case 0: //ACTION_DOWN
                    [x0, y0] = [event.getX(), event.getY()];
                    //break;
                case 2: //ACTION_MOVE
                    let [x1, y1] = [event.getX(), event.getY()];
                    let [dx, dy] = [(x1 - x0), (y1 - y0)] 
        
                    cameraMatrix = multiplyMatrices_4x4(rotation(dy / 1000, dx / 1000, 0), cameraMatrix);
                    Mortho = world_to_screen(cameraMatrix, perspectiveMatrix);
                
                    x0 = x1, y0 = y1;
                    break;
                case 1: //ACTION_UP
                    break;
                default:
                    break;
            }
            return true;
        })
    }

    _setUpRocker(canvasView, upBtn, downBtn) {
        const Paint = android.graphics.Paint;
        const paint = new Paint();
        paint.setColor(toColorInt(0xa0000000));
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(7)
        let circles = [];
        canvasView.on("draw", (canvas) => {
            circles.forEach((circle, index) => {
                canvas.drawCircle(circle.x, circle.y, circle.r, paint);
            })
        })
        let x0, y0, dx = 0,
            dy = 0,
            dz = 0;
        let movingTimer = setInterval(() => {
            if (dx || dy || dz) {
                cameraMatrix = multiplyMatrices_4x4(translation(-dx / 1000, dz / 1000, dy / 1000), cameraMatrix);
                Mortho = world_to_screen(cameraMatrix, perspectiveMatrix);
            }
        }, 30)
        canvasView.setOnTouchListener((view, event) => {
            //console.log(circles)
            switch (event.getAction()) {
                case 0: //ACTION_DOWN
                    [x0, y0] = [event.getX(), event.getY()];
                    circles.push({
                        x: x0,
                        y: y0,
                        r: 150
                    }, {
                        x: x0,
                        y: y0,
                        r: 50
                    });
                    //break;
                case 2: //ACTION_MOVE
                    let [x1, y1] = [event.getX(), event.getY()];
                    dx = (x1 - x0), dy = (y1 - y0);
                    circles[1] = {
                        x: x1,
                        y: y1,
                        r: 70
                    }
                    break;
                case 1: //ACTION_UP
                    circles = [];
                    dx = 0, dy = 0;
                    break;
                default:
                    break;
            }
            return true;
        })
        upBtn.setOnTouchListener((view, event) => {
            switch (event.getAction()) {
                case 0:
                    dz = 100;
                    break;
                case 1:
                    dz = 0;
                    break;
                default:
                    break;
            }
            return true
        })
        downBtn.setOnTouchListener((view, event) => {
            switch (event.getAction()) {
                case 0:
                    dz = -100;
                    break;
                case 1:
                    dz = 0;
                    break;
                default:
                    break;
            }
            return true
        })
    }

    _setUpCamere(fov, aspect, n, f, fvv, aspv, nv, farv) {
        fov.setOnSeekBarChangeListener({
            onProgressChanged: (view, progress, formUser) => {
                perspectiveMatrix = perspectiveProjection(progress * 0.01745329, 1.2 ** (10 - aspect.getProgress() / 10), n.getProgress() / 10, f.getProgress()); //Pi/180~~0.01746329
                Mortho = world_to_screen(cameraMatrix, perspectiveMatrix);
                fvv.setText(String(progress * 0.01745329))
            },
            onStartTrackingTouch: () => {},
            onStopTrackingTouch: () => {}
        })
        aspect.setOnSeekBarChangeListener({
            onProgressChanged: (view, progress, formUser) => {
                perspectiveMatrix = perspectiveProjection(fov.getProgress() * 0.01745329, 1.2 ** (10 - progress / 10), n.getProgress() / 10, f.getProgress()); //
                Mortho = world_to_screen(cameraMatrix, perspectiveMatrix);
                aspv.setText(String(1.2 ** (10 - progress / 10)));
            },
            onStartTrackingTouch: () => {},
            onStopTrackingTouch: () => {}
        })
        n.setOnSeekBarChangeListener({
            onProgressChanged: (view, progress, formUser) => {
                perspectiveMatrix = perspectiveProjection(fov.getProgress() * 0.01745329, 1.2 ** (10 - aspect.getProgress() / 10), progress / 20, f.getProgress()); //
                Mortho = world_to_screen(cameraMatrix, perspectiveMatrix);
                nv.setText(String(progress / 20))
            },
            onStartTrackingTouch: () => {},
            onStopTrackingTouch: () => {}
        })
        f.setOnSeekBarChangeListener({
            onProgressChanged: (view, progress, formUser) => {
                perspectiveMatrix = perspectiveProjection(fov.getProgress() * 0.01745329, 1.2 ** (10 - aspect.getProgress() / 10), n.getProgress() / 10, progress); //
                Mortho = world_to_screen(cameraMatrix, perspectiveMatrix);
                farv.setText(String(progress))
            },
            onStartTrackingTouch: () => {},
            onStopTrackingTouch: () => {}
        })
    }
}

ui.setMainActivity(MainActivity);

///////////////////////////////////////////////////////////////////////////////////////////////////////
//画物体
const drawObj = (canvas, paint, obj, Mortho, center, scale) => {
    let points_for_draw = [],
        lines_for_draw = [];
    paint.setColor(obj.color)

    obj.points.forEach((value, index) => {
        let temp = multiplyMatrixAndVector(Mortho, value); //投影到二维平面
        let Z = temp[2];
        temp[0] /= -Z;
        temp[1] /= -Z;
        
        points_for_draw.push(temp);
        
    })
   
    obj.connects.forEach((value, index) => {
        lines_for_draw.push([points_for_draw[value[0]][0], points_for_draw[value[0]][1], points_for_draw[value[1]][0], points_for_draw[value[1]][1]])
    })

    
    paint.setStrokeWidth(10); //画点
    points_for_draw.forEach((value, index) => {
        canvas.drawPoint(center.x + value[0] * scale, center.y + value[1] * scale, paint)
    })
    
   
    paint.setStrokeWidth(obj.width) //画连线
    lines_for_draw.forEach(value => {
        canvas.drawLine(center.x + value[0] * scale, center.y + value[1] * scale, center.x + value[2] * scale, center.y + value[3] * scale, paint)
    })
    
}


////////////////////////////////////////////////////////////////////////
//线性代数
const multiplyVectors_4x4 = (vector1, vector2) => { //向量乘法
    let result = 0;
    for (let i = 0; i < 4; i++) {
        if (vector1[i] && vector2[i]) { 
            result += vector1[i] * vector2[i];
        }
    }
    return result;
}

const multiplyMatrixAndVector = (matrix, vector) => { //四维向量被4x4矩阵变换 并归一化
    //依赖 multiplyVectors_4x4
    const
        resultX = multiplyVectors_4x4(matrix[0], vector),
        resultY = multiplyVectors_4x4(matrix[1], vector),
        resultZ = multiplyVectors_4x4(matrix[2], vector),
        resultW = multiplyVectors_4x4(matrix[3], vector);
    return [resultX / resultW, resultY / resultW, resultZ / resultW, 1];
}

const multiplyPointAndMatrix = (point, matrix) => { //矩阵乘法拆分步骤 左阵一行点积右阵
    const c0r0 = matrix[0][0],
        c1r0 = matrix[0][1],
        c2r0 = matrix[0][2],
        c3r0 = matrix[0][3];
    const c0r1 = matrix[1][0],
        c1r1 = matrix[1][1],
        c2r1 = matrix[1][2],
        c3r1 = matrix[1][3];
    const c0r2 = matrix[2][0],
        c1r2 = matrix[2][1],
        c2r2 = matrix[2][2],
        c3r2 = matrix[2][3];
    const c0r3 = matrix[3][0],
        c1r3 = matrix[3][1],
        c2r3 = matrix[3][2],
        c3r3 = matrix[3][3];
    const [x, y, z, w] = point;

    const result0 = (x * c0r0) + (y * c0r1) + (z * c0r2) + (w * c0r3),
        result1 = (x * c1r0) + (y * c1r1) + (z * c1r2) + (w * c1r3),
        result2 = (x * c2r0) + (y * c2r1) + (z * c2r2) + (w * c2r3),
        result3 = (x * c3r0) + (y * c3r1) + (z * c3r2) + (w * c3r3);
    return [result0, result1, result2, result3]
}
const multiplyMatrices_4x4 = (matrixA, matrixB) => { //矩阵乘法
    //依赖函数 multiplyPointAndMatrix
    const [v0, v1, v2, v3] = matrixA;
    var result0 = multiplyPointAndMatrix(v0, matrixB),
        result1 = multiplyPointAndMatrix(v1, matrixB),
        result2 = multiplyPointAndMatrix(v2, matrixB),
        result3 = multiplyPointAndMatrix(v3, matrixB);
    return [result0, result1, result2, result3];
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//矩阵变换
const orthogonalProjection = (l, r, t, b, n, f) => { //正交投影矩阵
    const Mortho = [
        [2 / (r - l), 0, 0, -(r + l) / (r - l)],
        [0, 2 / (t - b), 0, -(t + b) / (t - b)],
        [0, 0, -2 / (f - n), -(f + n) / (f - n)],
        [0, 0, 0, 1]
    ]
    return Mortho;
}

const perspectiveProjection = (fov, aspect, n, f) => { //透视投影矩阵
    const t = n * Math.tan(fov / 2);
    const b = -t;
    const r = t * aspect;
    const l = -r;
    const Mortho = [
        [2 * n / (r - l), 0, (r + l) / (r - l), 0],
        [0, 2 * n / (t - b), (t + b) / (t - b), 0],
        [0, 0, -(f + n) / (f - n), -2 * f * n / (f - n)],
        [0, 0, -1, 0]
    ]
    return Mortho;
}


const rotation = (i, j, k) => { //旋转变换
    const [sini, cosi, sinj, cosj, sink, cosk] = [Math.sin(i), Math.cos(i), Math.sin(j), Math.cos(j), Math.sin(k), Math.cos(k)]
    const RX = [
            [1, 0, 0, 0],
            [0, cosi, -sini, 0],
            [0, sini, cosi, 0],
            [0, 0, 0, 1]
        ],
        RY = [
            [cosj, 0, -sinj, 0],
            [0, 1, 0, 0],
            [sinj, 0, cosj, 0],
            [0, 0, 0, 1]
        ],
        RZ = [
            [cosk, -sink, 0, 0],
            [sink, cosk, 0, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        ]

    const Mortho = multiplyMatrices_4x4(RZ, multiplyMatrices_4x4(RY, RX));
    return Mortho;
}
const scale = (Sx, Sy, Sz) => { //缩放变换
    const Mortho = [
        [Sx, 0, 0, 1],
        [0, Sy, 0, 1],
        [0, 0, Sz, 1],
        [0, 0, 0, 1]
    ];
    return Mortho;
}

const translation = (Tx, Ty, Tz) => { //移动变换
    const Mortho = [
        [1, 0, 0, Tx],
        [0, 1, 0, Ty],
        [0, 0, 1, Tz],
        [0, 0, 0, 1]
    ];
    return Mortho;
}

const scaleAndTranslation = (Sx, Sy, Sz, Tx, Ty, Tz) => { //移动缩放变换
    const Mortho = [
        [Sx, 0, 0, Tx],
        [0, Sy, 0, Ty],
        [0, 0, Sz, Tz],
        [0, 0, 0, 1]
    ];
    return Mortho;
}


const world_to_camera = (x, y, z, i, j, k) => { //世界坐标转相机坐标的矩阵
    const Mortho = rotation(i, j, k);
    Mortho[0][3] = x, Mortho[1][3] = y, Mortho[2][3] = z;//比矩阵乘法更快
    return Mortho
}

const world_to_screen = (camereMatriix, perspectiveMatrix) => { //世界坐标转屏幕坐标的矩阵
    //依赖 world_to_camera
    const Mortho = multiplyMatrices_4x4(perspectiveMatrix, camereMatriix);
    return Mortho;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////
//图形函数
var perspectiveMatrix = perspectiveProjection(0.3, 1, 11, 100); //透视投影矩阵可能动态变化

var cameraMatrix = world_to_camera(5, 5, 5, -2, 0, -3.14);
var Mortho = world_to_screen(cameraMatrix, perspectiveMatrix);


const cube = { //测试用的2x2x2立方体
    color: toColorInt(0xff000000),
    width: 2,
    points: [
        [1, 1, -1, 1],
        [1, -1, -1, 1],
        [-1, -1, -1, 1],
        [-1, 1, -1, 1],
        [1, 1, 1, 1],
        [1, -1, 1, 1],
        [-1, -1, 1, 1],
        [-1, 1, 1, 1]
    ],
    connects: [
        [0, 1],
        [1, 2],
        [2, 3],
        [3, 0],
        [4, 5],
        [5, 6],
        [6, 7],
        [7, 4],
        [0, 4],
        [1, 5],
        [2, 6],
        [3, 7]
    ]
}
const triangularPyramid = { //测试用的正四面体
    color: toColorInt(0xff000000),
    width: 2,
    points: [
        [0, 1, 0, 1],
        [-0.866, -0.5, 0, 1],
        [0.866, -0.5, 0, 1],
        [0, 0, 1.414, 1]
    ],
    connects: [
        [0, 1],
        [1, 2],
        [2, 3],
        [3, 0],
        [1, 3],
        [2, 0]
    ]
}
const
    Ox = { //x轴
        color: toColorInt(0xffff0000),
        width: 3,
        points: [
            [0, 0, 0, 1],
            [10, 0, 0, 1]
        ],
        connects: [
            [0, 1]
        ]
    },
    Oy = { //y轴
        color: toColorInt(0xff00ff00),
        width: 3,
        points: [
            [0, -1, 0, 1],
            [0, 10, 0, 1]
        ],
        connects: [
            [0, 1]
        ]
    },
    Oz = { //z轴
        color: toColorInt(0xff0000ff),
        width: 3,
        points: [
            [0, 0, -1, 1],
            [0, 0, 10, 1]
        ],
        connects: [
            [0, 1]
        ]
    }

const getPlaneMesh_XY = (min, max, d) => { //XY平面网格
    let obj = {
        color: toColorInt(0xffffffff),
        width: 1,
        points: [], //局部坐标
        connects: []
    };
    for (let i = min; i <= max; i += d) {
        obj.points.push([i, max, 0, 1], [i, min, 0, 1], [min, i, 0, 1], [max, i, 0, 1]);
    }
    for (let i = 0; i < obj.points.length; i += 2) {
        obj.connects.push([i, i + 1])
    }
    return obj;
}

const getPrismMesh = (r, n, h) => { //多边形棱柱  半径 边数 高度
    let obj = {
        color: toColorInt(0xff000000),
        width: 2,
        points: [], //局部坐标
        connects: []
    };
    let z = h / 2;
    let d_angle = Math.PI * 2 / n;
    for (let i = 0; i < n; i++) {
        let [x1, y1] = [r * Math.cos(i * d_angle), r * Math.sin(i * d_angle)];
        obj.points.push([x1, y1, z, 1], [x1, y1, -z, 1]);
    }

    for (let i = 0; i < obj.points.length - 2; i++) {
        obj.connects.push([i, i + 2]);
        if (!(i % 2)) obj.connects.push([i, i + 1])
    }
    obj.connects.push([0, 2 * n - 2], [1, 2 * n - 1], [2 * n - 2, 2 * n - 1]);
    return obj;
}

const defaultOption = {
    color: toColorInt(0xff000000),
    width: 2
}

function CreateObject(mesh, Rx, Ry, Rz, Sx, Sy, Sz, Tx, Ty, Tz, options) { //图 旋转 缩放 平移
    this.getRotation = {
        Rx: Rx,
        Ry: Ry,
        Rz: Rz
    }
    this.getScale = {
        Sx: Sx,
        Sy: Sy,
        Sz: Sz
    }
    this.getTranslation = {
        Tx: Tx,
        Ty: Ty,
        Tz: Tz
    }
    this.setRotation = (rx, ry, rz) => {
        this.getRotation = {
            Rx: rx,
            Ry: ry,
            Rz: rz
        }
    }
    this.setScale = (sx, sy, sz) => {
        this.getScale = {
            Sx: sx,
            Sy: sy,
            Sz: sz
        }
    }
    this.setTranslation = (tx, ty, tz) => {
        this.getTranslation = {
            Tx: tx,
            Ty: ty,
            Tz: tz
        }
    }
    this.getMortho = () => {
        const [r, s, t] = [this.getRotation, this.getScale, this.getTranslation];
        const rotationMatrix = rotation(r.Rx, r.Ry, r.Rz);
        const translationMatrix = scaleAndTranslation(s.Sx, s.Sy, s.Sz, t.Tx, t.Ty, t.Tz);
        return multiplyMatrices_4x4(rotationMatrix, translationMatrix);
    }
    this.getWorldCoordinates = () => {
        let obj = {
            color: options.color,
            width: options.width,
            points: [],
            connects: mesh.connects
        }
        const Mortho = this.getMortho();
        mesh.points.forEach((value, index) => {
            obj.points.push(multiplyMatrixAndVector(Mortho, value));
        })
        return obj;
    }
}

let testObj = new CreateObject(getPrismMesh(2, 10, 2), 0, 0, 0, 1, 1, 2, 0, 0, 0, defaultOption);

let Cylinder = getPrismMesh(1, 1, 1, 10, 2)
let objs = [Ox,Oy,Oz,testObj.getWorldCoordinates(),getPlaneMesh_XY(-10,10,1)]; ////////////////////////////////////////////////////////////////////////////


let qwere = 0;
setInterval(() => {
    testObj.setRotation((++qwere) / 10, 0, 0)
    //testObj.setTranslation((++qwere) / 10, 0, 0)
    objs[3] = testObj.getWorldCoordinates()
}, 10)
